/*
Night Shell Switcher Gnome Shell extension

Copyright (C) 2020 Romain Vigier

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program. If not, see <http s ://www.gnu.org/licenses/>.
*/

const { extensionUtils } = imports.misc;
const { Gio } = imports.gi;
const { main } = imports.ui;

const Me = extensionUtils.getCurrentExtension();
const config = Me.imports.config;

const { log_debug } = Me.imports.utils;

const { Variants } = Me.imports.modules.Variants;

const Gettext = imports.gettext.domain(config.EXT_UUID);
const _ = Gettext.gettext;


/*
The Themer communicates with the system to get the current theme or set a new
one, and get the day and night variants of a theme. It listens to theme changes
and reports them.

As the User Themes extension is essential for the extension to work, it checks
if it is installed and enabled and warns the user if that's not the case. To
not overwhelm the user with notifications, it only warns once and then only
listens to extensions changes to reactivate itself automatically.
*/

var Themer = class {

	constructor() {
		log_debug('Initializing Themer...');
		this.settings = extensionUtils.getSettings();
		this.warn_userthemes_extension_not_installed = true;
		this.warn_userthemes_extension_not_enabled = true;
		log_debug('Themer initialized.');
	}

	enable() {
		log_debug('Enabling Themer...');
		try {
			this.ready = false;
			this.userthemes_extension_ready = false;
			this._listen_to_userthemes_extension_install();
			this._listen_to_userthemes_extension_status();
			this._check_userthemes_extension_installed();
			this.userthemes_extension = this._get_userthemes_extension();
			this.userthemes_gsettings = this._get_userthemes_gsettings();
			this._check_userthemes_extension_status();
			this.userthemes_extension_ready = true;
			this._listen_to_theme_changes();
			this.update_variants();
			this.ready = true;
			this.emit();
			log_debug('Themer enabled.');
		}
		catch(e) {
			if ( e.message ) {
				main.notifyError(config.EXT_NAME, e.message);
			}
		}
	}

	disable() {
		log_debug('Disabling Themer...');
		this._stop_listening_to_userthemes_extension_install();
		this._stop_listening_to_userthemes_extension_status();
		this._stop_listening_to_theme_changes();
		// GNOME Shell disables extensions when locking the screen. We'll only
		// reset the theme if the user disables the extension to prevent
		// flickering when unlocking.
		if ( !main.screenShield.locked ) {
			this.reset_theme();
		}
		log_debug('Themer disabled.');
	}

	get current_theme() {
		if ( this.userthemes_extension_ready ) {
			return this.userthemes_gsettings.get_string(config.THEME_GSETTINGS_PROPERTY);
		}
	}

	set current_theme(theme) {
		if ( this.userthemes_extension_ready && theme !== this.current_theme ) {
			this.userthemes_gsettings.set_string(config.THEME_GSETTINGS_PROPERTY, theme);
			log_debug(`Theme has been set to "${theme}".`);
		}
	}

	subscribe(callback) {
		this.theme_change_callback = callback;
	}

	emit() {
		if ( this.theme_change_callback ) {
			this.theme_change_callback();
		}
	}

	set_variant(variant) {
		if ( this.ready ) {
			log_debug(`Setting theme to the "${variant}" variant...`);
			this.current_theme = this.settings.get_string(`theme-${variant}`);
		}
	}

	reset_theme() {
		this.set_variant('original');
		log_debug('Theme has been reset to the user\'s original variant.')
	}

	update_variants() {
		if ( !this._are_variants_up_to_date() ) {
			this._update_variants();
		}
	}

	_update_variants() {
		if ( this.current_theme ) {
			const variants = Variants.guess_from(this.current_theme);
			variants.forEach( (theme, variant) => this.settings.set_string(`theme-${variant}`, theme) );
			log_debug(`Variants updated: {day: "${variants.get('day')}", night: "${variants.get('night')}"}`);
		}
	}

	_are_variants_up_to_date() {
		return ( this.current_theme === this.settings.get_string('theme-day') || this.current_theme === this.settings.get_string('theme-night') );
	}

	_get_userthemes_gsettings() {
		log_debug('Getting User Themes extension GSettings...');
		if ( this.userthemes_gsettings ) {
			return this.userthemes_gsettings;
		}
		else {
			const schemaDir = this.userthemes_extension.dir.get_child('schemas');
			const GioSSS = Gio.SettingsSchemaSource;
			let schemaSource;
			if ( schemaDir.query_exists(null) ) {
				schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false);
			}
			else {
				schemaSource = GioSSS.get_default();
			}
			const schemaObj = schemaSource.lookup(config.THEME_GSETTINGS_SCHEMA, true);
			return new Gio.Settings({ settings_schema: schemaObj });
		}
	}

	_get_userthemes_extension() {
		if ( this.userthemes_extension ) {
			return this.userthemes_extension;
		}
		else {
			log_debug('Getting User Themes extension...');
			return main.extensionManager.lookup(config.USERTHEMES_UUID);
		}
	}

	_is_userthemes_extension_installed() {
		return !!(main.extensionManager.lookup(config.USERTHEMES_UUID));
	}

	_check_userthemes_extension_installed() {
		if ( !this._is_userthemes_extension_installed() ) {
			if ( this.warn_userthemes_extension_not_installed && !main.screenShield.locked ) {
				this.warn_userthemes_extension_not_installed = false;
				const message = _('"User Themes" GNOME Shell extension must be installed, please install and enable it.');
				throw new Error(message);
			}
			else {
				throw new Error();
			}
		}
	}

	_listen_to_userthemes_extension_install() {
		if ( !this.userthemes_extension_install_connect ) {
			this.userthemes_extension_install_connect = main.extensionManager.connect(
				'extension-loaded',
				this._on_userthemes_extension_install_change.bind(this)
			);
			log_debug('Listening for the installation of the User Themes extension...');
		}
	}

	_stop_listening_to_userthemes_extension_install() {
		if ( this.userthemes_extension_install_connect ) {
			main.extensionManager.disconnect(this.userthemes_extension_install_connect);
			this.userthemes_extension_install_connect = null;
			log_debug('Stopped listening for the installation of the User Themes extension.');
		}
	}

	_on_userthemes_extension_install_change(emitter, uuid) {
		if ( uuid === config.USERTHEMES_UUID ) {
			log_debug('User Themes extension has been installed.');
			this.enable();
		}
	}

	_is_userthemes_extension_enabled() {
		return (this.userthemes_extension.state === 1);
	}

	_check_userthemes_extension_status() {
		if ( !this._is_userthemes_extension_enabled() ) {
			if ( this.warn_userthemes_extension_not_enabled && !main.screenShield.locked ) {
				this.warn_userthemes_extension_not_enabled = false;
				const message = _('"User Themes" GNOME Shell extension must be enabled, please enable it.');
				throw new Error(message);
			}
			else {
				throw new Error();
			}
		}
	}

	_listen_to_userthemes_extension_status() {
		if ( !this.userthemes_extension_status_connect ) {
			this.userthemes_extension_status_connect = main.extensionManager.connect(
				'extension-state-changed',
				this._on_userthemes_extension_status_change.bind(this)
			);
			log_debug('Listening for User Themes extension status changes...');
		}
	}

	_stop_listening_to_userthemes_extension_status() {
		if ( this.userthemes_extension_status_connect ) {
			main.extensionManager.disconnect(this.userthemes_extension_status_connect);
			this.userthemes_extension_status_connect = null;
			log_debug('Stopped listening for User Themes extension status changes.');
		}
	}

	_on_userthemes_extension_status_change(emitter, extension) {
		if ( extension.uuid === config.USERTHEMES_UUID ) {
			log_debug('User Themes status has changed.');
			this.enable();
		}
	}

	_listen_to_theme_changes() {
		if ( !this.theme_change_connect ) {
			this.theme_change_connect = this.userthemes_gsettings.connect(
				'changed::' + config.THEME_GSETTINGS_PROPERTY,
				this._on_theme_change.bind(this)
			);
			log_debug('Listening for theme changes...');
		}
	}

	_stop_listening_to_theme_changes() {
		if ( this.userthemes_gsettings && this.theme_change_connect ) {
			this.userthemes_gsettings.disconnect(this.theme_change_connect);
			this.theme_change_connect = null;
			log_debug('Stopped listening for theme changes.');
		}
	}

	_on_theme_change() {
		log_debug(`Theme has changed to "${this.current_theme}".`);
		this.emit();
	}

}
